use crate::api::model::{ContentType, HttpMethod, RequestSpec};
use reqwest::header::HeaderName;
use reqwest::{Client, Method, RequestBuilder};
use serde::Serialize;
use snafu::{ResultExt, ensure_whatever};

pub fn build_request<T: Serialize + ?Sized>(
    client: &Client,
    spec: &RequestSpec,
    payload: Option<&T>,
) -> crate::Result<RequestBuilder> {
    let url = join_url(&spec.base_url, &spec.endpoint);
    let method = to_method(spec.method);
    let mut rb = client.request(method, url);

    if let Some(dur) = spec.timeout {
        rb = rb.timeout(dur);
    }

    for (k, v) in &spec.headers {
        let name = HeaderName::from_bytes(k.as_bytes())
            .whatever_context(format!("Invalid header name: {}", k))?;
        rb = rb.header(name, v);
    }

    ensure_whatever!(
        spec.method != HttpMethod::GET || payload.is_none(),
        "Invalid method"
    );
    if let Some(payload) = payload {
        rb = match spec.content_type {
            ContentType::Json => rb.json(payload),
            ContentType::Form => rb.form(payload),
        }
    }

    Ok(rb)
}

fn join_url(base: &str, endpoint: &str) -> String {
    format!(
        "{}/{}",
        base.trim_end_matches('/'),
        endpoint.trim_start_matches('/')
    )
}

fn to_method(m: HttpMethod) -> Method {
    match m {
        HttpMethod::GET => Method::GET,
        HttpMethod::POST => Method::POST,
    }
}
